﻿/*
Copyright 2010-2011 Thomas W. Holtquist

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

using System;
using System.Threading;
using System.IO;

using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;
using Microsoft.SPOT.IO;
using Microsoft.SPOT.Presentation.Media;

using GHIElectronics.NETMF.Hardware;
using GHIElectronics.NETMF.IO;
using GHIElectronics.NETMF.System;
using GHIElectronics.NETMF.USBHost;

namespace Pyxis2Updater
{
    public class Program
    {

        static int ScreenWidth;
        static int ScreenHeight;
        static Bitmap bmp;
        static Font fnt = Resources.GetFont(Resources.FontResources.tahoma11);
        static Font bld = Resources.GetFont(Resources.FontResources.tahoma11_bold);
        static int y = 4;
        static string sRoot = "\\";
        static PersistentStorage _sd;
        static bool Installing = false;
        static VolumeInfo activeVol;
        static PersistentStorage USB;

        public static void Main()
        {
            int bitsPerPixel, orientationDeg;

            if (SystemUpdate.GetMode() != SystemUpdate.SystemUpdateMode.Bootloader) throw new Exception("Invalid mode!");

            // Get system metrics
            HardwareProvider.HwProvider.GetLCDMetrics(out ScreenWidth, out ScreenHeight, out bitsPerPixel, out orientationDeg);

            // Create the bitmap
            bmp = new Bitmap(ScreenWidth, ScreenHeight);
            bmp.DrawRectangle(Color.Black, 0, 0, 0, bmp.Width, bmp.Height, 0, 0, Color.Black, 0, 0, Color.Black, 0, 0, 255);
            bmp.DrawText("Pyxis 2 System Update", bld, Color.White, 4, y);
            y += bld.Height + 4;
            bmp.DrawText("Version 1.2", fnt, Color.White, 4, y);
            y += (fnt.Height + 4) * 2;
            bmp.DrawText("Enabling SD...", fnt, Color.White, 4, y);
            bmp.Flush();

            // Subscribe to RemovableMedia events
            RemovableMedia.Insert += RemovableMedia_Insert;

            // Enable SD Card
            try
            {
                _sd = new PersistentStorage("SD");
                _sd.MountFileSystem();
                bmp.DrawText("OK", bld, Color.White, 4 + TextWidth("Enabling SD...", fnt), y);
                bmp.Flush();
                y += fnt.Height + 4;
            }
            catch (Exception)
            {
                bmp.DrawText("FAILED", bld, Color.White, 4 + TextWidth("Enabling SD...", fnt), y);
                bmp.Flush();
                y += fnt.Height + 4;
            }

            // Subscribe to USBH events.
            USBHostController.DeviceConnectedEvent += DeviceConnectedEvent;

            // Give USB time to connect
            Thread.Sleep(Timeout.Infinite);

        }

        static int TextWidth(string text, Font fnt)
        {
            int w, h;
            fnt.ComputeExtent(text, out w, out h);
            return w;
        }

        static void DeviceConnectedEvent(USBH_Device device)
        {
            switch (device.TYPE)
            {
                case USBH_DeviceType.MassStorage:
                    try
                    {
                        USB = null;
                        USB = new PersistentStorage(device);
                        USB.MountFileSystem();
                    }
                    catch (Exception)
                    {
                    }
                    break;
                default:
                    Debug.Print("USB type not yet implemented.");
                    break;
            }
        }

        static void RemovableMedia_Insert(object sender, MediaEventArgs e)
        {
            if (Installing) return;

            bmp.DrawText("Searching '" + NormalizeDirectory(e.Volume.RootDirectory) + "' for updates...", fnt, Color.White, 4, y);
            bmp.Flush();

            if (File.Exists(NormalizeDirectory(e.Volume.RootDirectory) + "bootloaderdemand.txt"))
            {
                y += (fnt.Height + 4) * 2;
                bmp.DrawText("Boot Loader Mode DEMANDED", bld, Color.White, 4, y);
                y += (fnt.Height + 4) * 2;
                bmp.DrawText("System Halted", bld, Color.White, 4, y);
                bmp.Flush();
                return;
            }

            if (File.Exists(NormalizeDirectory(e.Volume.RootDirectory) + "applicationdemand.txt"))
            {
                y += (fnt.Height + 4) * 2;
                bmp.DrawText("Application Mode DEMANDED", bld, Color.White, 4, y);
                bmp.Flush();
                Thread.Sleep(1000);
                SystemUpdate.AccessApplication();
                return;
            }

            try
            {
                sRoot = NormalizeDirectory(e.Volume.RootDirectory) + "pyxis\\system\\";
                if (File.Exists(sRoot + "bootloader.hex"))
                {
                    bmp.DrawText("FOUND UPDATES", bld, Color.White, 4 + TextWidth("Searching '" + NormalizeDirectory(e.Volume.RootDirectory) + "' for updates...", fnt), y);
                    bmp.Flush();
                    y += (fnt.Height + 4) * 2;

                    activeVol = e.Volume;
                    InstallCompleteUpdate(sRoot);
                }
                else if (File.Exists(sRoot + "pyxis2.hex"))
                {
                    bmp.DrawText("FOUND UPDATES", bld, Color.White, 4 + TextWidth("Searching '" + NormalizeDirectory(e.Volume.RootDirectory) + "' for updates...", fnt), y);
                    bmp.Flush();
                    y += (fnt.Height + 4) * 2;

                    activeVol = e.Volume;
                    InstallUpdate(sRoot + "pyxis2.hex");
                }
                else
                {
                    bmp.DrawText("NOT FOUND", bld, Color.White, 4 + TextWidth("Searching '" + NormalizeDirectory(e.Volume.RootDirectory) + "' for updates...", fnt), y);
                    bmp.Flush();
                    y += fnt.Height + 4;
                }
            }
            catch (Exception)
            {
                bmp.DrawText("FAILED", bld, Color.White, 4 + TextWidth("Searching drive for updates...", fnt), y);
                bmp.Flush();
                y += fnt.Height + 4;
            }
        }

        /// <summary>
        /// Ensures the directory ends with a '\' character
        /// </summary>
        /// <param name="Path"></param>
        /// <returns></returns>
        static string NormalizeDirectory(string Path)
        {
            if (Path.Substring(Path.Length - 1) != "\\") return Path + "\\";
            return Path;
        }

        static void InstallUpdate(string path)
        {
            bool bComplete = true;

            Installing = true;

            bmp.DrawText("INSTALLING UPDATES...", bld, Color.White, 4, y);
            bmp.Flush();


            // start update
            SystemUpdate.ApplicationUpdate.Start();

            if (!InstallFile(path))
            {
                bmp.DrawText("FAILED", bld, Color.White, 4 + TextWidth("INSTALLING UPDATES...", bld), y);
                bmp.Flush();
                bComplete = false;
                return;
            }

            try
            {
                File.Delete(path);
                activeVol.FlushAll();
            }
            catch (Exception)
            {
            }

            // End update
            if (bComplete) SystemUpdate.ApplicationUpdate.End();

            // Access the application
            SystemUpdate.AccessApplication();
        }

        static void InstallCompleteUpdate(string path)
        {
            bool bComplete = true;

            Installing = true;

            bmp.DrawText("INSTALLING UPDATES...", bld, Color.White, 4, y);
            bmp.Flush();


            // start update
            SystemUpdate.CompleteUpdate.Start();

            string[] files;
            switch (SystemInfo.SystemID.Model)
            {
                case 5: // Cobra
                    files = new string[] { "CLR.HEX", "CLR2.HEX", "Config.HEX", "pyxis2.hex", "bootloader.hex" };                   
                    break;
                case 6: // Chipworks
                    files = new string[] { "ER_FLASH", "ER_CONFIG", "pyxis2.hex", "bootloader.hex" };
                    break;
                default: // Unknown
                    bmp.DrawText("FAILED [0x" + SystemInfo.SystemID.Model + "]", bld, Color.White, 4 + TextWidth("INSTALLING UPDATES...", bld), y);
                    bmp.Flush();
                    Thread.Sleep(1000);
                    SystemUpdate.AccessApplication();
                    return;
            }

            for (int i = 0; i < files.Length; i++)
            {
                y += (fnt.Height + 4);
                bmp.DrawText(">> " + files[i] + "...", fnt, Color.White, 50, y);
                bmp.Flush();
                if (!CompleteInstallFile(path + files[i]))
                {
                    bmp.DrawText("FAILED", bld, ColorUtility.ColorFromRGB(255, 0, 0), 50 + TextWidth(">> " + files[i] + "...", fnt), y);
                    bmp.Flush();
                    bComplete = false;
                    Thread.Sleep(1000);
                    break;
                }
                else
                {
                    bmp.DrawText("OK", bld, ColorUtility.ColorFromRGB(0, 255, 0), 50 + TextWidth(">> " + files[i] + "...", fnt), y);
                    bmp.Flush();
                }

                try
                {
                    File.Delete(path + files[i]);
                }
                catch (Exception e)
                {
                    Debug.Print("Couldn't delete '" + files[i] + "'\r\n  >>" + e.Message);
                }

            }

            // End update
            if (bComplete) SystemUpdate.CompleteUpdate.End();

            // Access the application
            SystemUpdate.AccessApplication();
        }

        private static bool InstallFile(string sFile)
        {
            try
            {
                FileStream file = new FileStream(sFile, FileMode.Open, FileAccess.Read);

                // read the file in chucks
                byte[] buffer = new byte[10 * 1024];
                int length;

                do
                {
                    length = file.Read(buffer, 0, buffer.Length);
                    SystemUpdate.ApplicationUpdate.Write(buffer, 0, length);

                } while (length == buffer.Length);

                file.Close();
                return true;
            }
            catch (Exception)
            {
                return false;
            }
        }

        private static bool CompleteInstallFile(string sFile)
        {
            try
            {
                FileStream file = new FileStream(sFile, FileMode.Open, FileAccess.Read);

                // read the file in chucks
                byte[] buffer = new byte[10 * 1024];
                int length;

                do
                {
                    length = file.Read(buffer, 0, buffer.Length);
                    SystemUpdate.CompleteUpdate.Write(buffer, 0, length);

                } while (length == buffer.Length);

                file.Close();
                return true;
            }
            catch (Exception)
            {
                return false;
            }
        }

    }
}
